﻿using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace MP2
{
    class XEXInfo
    {
        #region Global Variable Declaration
        //Constant vars required in the XDVDFS.
        const long baseoffset = 0x00FD90000;
        const string mediaCheck = "MICROSOFT*XBOX*MEDIA";

        //Used in Sector32Check() and RootDirectoryTable().
        private char[] SectorDatastore = new char[0x800];
        FileStream fsIO;
        BinaryReader binIO;

        //Required by: this.XEXInformation().
        int XEX_SIZE;
        uint RAW_XEX_OFFSET;

        //For GUI Output:
        string XEX_CRC;
        #endregion

        /// <summary>
        /// Checks for the mediaCheck string at Sector 32.
        /// MSXBMEDIA is found here for ALL Xbox 360 images.
        /// </summary>
        /// <param name="isoLocation">The filepath to the target ISO image.</param>
        /// <returns>Boolean, true if it passes the check, else false.</returns>
        public bool Sector32Check(string isoLocation)
        {
            string XboxMediaString = null;
            //This generates the sector 32 offset.
            long SectorOffset = baseoffset + (32 * 0x800);

            //Build the IO to the ISO. (UTF7 for compat. with 65533)
            fsIO = new FileStream(isoLocation, FileMode.Open);
            binIO = new BinaryReader(fsIO, Encoding.UTF7);
            GC.SuppressFinalize(binIO.BaseStream);
            try
            {
                //Goto Sector offset and read the sector into char[].
                binIO.BaseStream.Position = SectorOffset;
                binIO.Read(SectorDatastore, 0, 0x800);

                //Check the Sector 32 for "MICROSOFT*XBOX*MEDIA"
                for (int i = 0; i < 20; i++)
                { XboxMediaString += SectorDatastore[i].ToString(); }
            }
            catch (System.Exception ex)
            { MessageBox.Show(ex.Message, "Mousepatcher 2.0"); }

            if (XboxMediaString == mediaCheck) { return true; }
            else { return false; }
        }

        /// <summary>
        /// Finds the RootDirTable Sector & Size.
        /// Reads the RDT until it finds default.xex, and it's offset and size etc.
        /// </summary>
        public bool RootDirectoryTable()
        {
            string RDT_SECTOR_STRING = null;
            string RDT_SIZE_STRING = null;

            //Hexadecimal representation of the RDT Sector Offset. 
            for (int i = 24; i >= 20; i--)
            { RDT_SECTOR_STRING += ConvertToHex(SectorDatastore[i].ToString()); }

            //Hex representation of the size of the RDT.
            for (int i = 27; i >= 24; i--)
            { RDT_SIZE_STRING += ConvertToHex(SectorDatastore[i].ToString()); }

            //Converts the Size and Sector into integers.
            int RDT_SIZE = int.Parse(RDT_SIZE_STRING, NumberStyles.HexNumber);
            int RDT_SECTOR = int.Parse(RDT_SECTOR_STRING, NumberStyles.HexNumber);

            //This generates the RDT Sector offset.
            //Then moves to the offset in the BinaryReader.
            uint RDTSectorOffset = (uint)(baseoffset + (RDT_SECTOR * 0x800));
            binIO.BaseStream.Position = RDTSectorOffset;

            //Start parsing the Directory Table Entries
            while (binIO.BaseStream.Position != (RDTSectorOffset + RDT_SIZE))
            {
                try
                {
                    //Read through the L&R Trees.
                    binIO.ReadChars(4);

                    //Read the Starting Sector for this Entry.
                    byte[] START_SECTOR = binIO.ReadBytes(4);

                    //Read the Size of this Entry.
                    byte[] FILE_SIZE = binIO.ReadBytes(4);

                    //Skip FAT Attributes. Not needed.
                    binIO.ReadChar();

                    //Get Length of File Name.
                    char[] NAME_LENGTH = binIO.ReadChars(1);

                    //File Name String.
                    char[] namearray = binIO.ReadChars(NAME_LENGTH[0]);
                    string FILE_NAME = new string(namearray).ToLower();

                    //Is the current file's name, "default.xex"?
                    if (FILE_NAME == "default.xex")
                    {
                        string XEX_SIZE_STRING = null;
                        string XEX_SECTOR_STRING = null;

                        //Find the XEX Size from the FILE_Size array.
                        Array.Reverse(FILE_SIZE);
                        XEX_SIZE_STRING = BitConverter.ToString(FILE_SIZE);
                        XEX_SIZE_STRING = XEX_SIZE_STRING.Replace("-", "");
                        XEX_SIZE = int.Parse(XEX_SIZE_STRING, NumberStyles.HexNumber);

                        //Find the XEX Starting Sector from the START_SECTOR array.
                        Array.Reverse(START_SECTOR);
                        XEX_SECTOR_STRING = BitConverter.ToString(START_SECTOR);
                        XEX_SECTOR_STRING = XEX_SECTOR_STRING.Replace("-", "");
                        uint XEX_SECTOR = uint.Parse(XEX_SECTOR_STRING, NumberStyles.HexNumber);

                        //Find actual XEX_OFFSET for use in application.
                        RAW_XEX_OFFSET = (uint)(baseoffset + (XEX_SECTOR * 0x800));
                        string XEX_OFFSET = ToHexString(RAW_XEX_OFFSET);
                        return true;
                    }
                    else
                    { while (binIO.PeekChar() == 'ÿ') { binIO.ReadChar(); } }
                }

                catch (Exception ex)
                { MessageBox.Show(ex.Message, "Mousepatcher 2", MessageBoxButtons.OK, MessageBoxIcon.Error); }
            }
            return false;
        }

        /// <summary>
        /// Calculates the CRC32 for the XEX.
        /// Pads with 0s if required for ABGX.
        /// </summary>
        /// <returns>CRC32 String for current XEX.</returns>
        public string XEXInformation()
        {
            try
            {
                //Goto XEX_OFFSET in BinaryReader.
                binIO.BaseStream.Position = RAW_XEX_OFFSET;

                //Read XEX into a FileStream buffer.
                byte[] XEX_BUFFER = new byte[XEX_SIZE];
                binIO.Read(XEX_BUFFER, 0, XEX_BUFFER.Length);

                //New Instance of CRC32. Get the CRC32 of the XEX.
                Crc32 myCRC32 = new Crc32();
                string CRC = String.Empty;

                myCRC32.ComputeHash(XEX_BUFFER);
                uint crc = myCRC32.CrcValue;
                CRC = ToHexString(myCRC32.CrcValue);

                if (CRC.Length < 8)
                {
                    //ABGX only accepts 8-Char CRC strings. Makes sense.
                    CRC = CRC.PadLeft(8, '0');
                }
                fsIO.Close();
                return CRC;
            }
            catch (Exception ex)
            { MessageBox.Show(ex.Message, "Mousepatcher 2.0", MessageBoxButtons.OK, MessageBoxIcon.Error); return "CRC32 FAILURE"; }

        }

        #region Supporting Tools. Mainly conversions from Hex to Decimal & vice versa.
        private string ToHexString(long DecOffset)
        {
            string hexValue = DecOffset.ToString("X");
            return hexValue;
        }
        private string ConvertToHex(string asciiString)
        {
            string hex = null;
            foreach (char c in asciiString)
            {
                int tmp = c;
                hex += String.Format("{0:X2}", (uint)System.Convert.ToUInt32(tmp.ToString()));
            }
            return hex;
        }
        #endregion
    }
}
